/*
 * Copyright 2011 Noctarius (Christoph Engelbert)
 * Part of www.sourceprojects.org 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may 
 * not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 *    http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package org.sourceprojects.cappadocia;

import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.log4j.Logger;

class MethodInfoImpl implements MethodInfo, AnnotatedElement,
		AnnotatedTypeElement {

	private static final Logger LOG = Logger.getLogger(MethodInfoImpl.class);

	private final ClassPool classPool;

	private final Collection<AnnotationInfo> annotations = new HashSet<AnnotationInfo>();
	private final Collection<ClassInfo> exceptions = new HashSet<ClassInfo>();

	private AnnotationInfo[][] paramAnnotations;

	private final String name;
	private final String signature;
	private final int modifiers;

	private final Set<String> exceptionClasses = new HashSet<String>();
	private final Lock exceptionLock = new ReentrantLock();

	private final Map<Integer, Collection<AnnotationInfo>> parameterAnnotations = new HashMap<Integer, Collection<AnnotationInfo>>();
	private final Lock parameterAnnotationsLock = new ReentrantLock();

	MethodInfoImpl(final ClassPool classPool, final String name,
			final String signature, final int modifiers) {

		this.classPool = classPool;
		this.name = name;
		this.signature = signature;
		this.modifiers = modifiers;
	}

	@Override
	public Collection<AnnotationInfo> getAnnotations() {
		return Collections.unmodifiableCollection(annotations);
	}

	@Override
	public AnnotationInfo[][] getParameterAnnotations() {
		parameterAnnotationsLock.lock();
		try {
			checkParameterAnnotationsResolving();

			final AnnotationInfo[][] temp = new AnnotationInfo[paramAnnotations.length][];
			for (int index = 0; index < temp.length; index++) {
				temp[index] = Arrays.copyOf(paramAnnotations[index],
						paramAnnotations[index].length);
			}

			return temp;

		} finally {
			parameterAnnotationsLock.unlock();
		}
	}

	@Override
	public Collection<ClassInfo> getExceptions() {
		exceptionLock.lock();
		try {
			checkExceptionsResolving();
			return Collections.unmodifiableCollection(exceptions);

		} finally {
			exceptionLock.unlock();
		}
	}

	@Override
	public String getName() {
		return name;
	}

	@Override
	public String getSignature() {
		return signature;
	}

	@Override
	public boolean isStatic() {
		return Modifier.isStatic(modifiers);
	}

	@Override
	public boolean isPublic() {
		return Modifier.isPublic(modifiers);
	}

	@Override
	public boolean isPrivate() {
		return Modifier.isPrivate(modifiers);
	}

	@Override
	public boolean isProtected() {
		return Modifier.isProtected(modifiers);
	}

	@Override
	public boolean isPackagePrivate() {
		return !isPrivate() && !isPublic() && !isProtected();
	}

	@Override
	public boolean isFinal() {
		return Modifier.isFinal(modifiers);
	}

	@Override
	public boolean isAbstract() {
		return Modifier.isAbstract(modifiers);
	}

	@Override
	public void addAnnotation(final AnnotationInfo annotationInfo) {
		annotations.add(annotationInfo);
	}

	@Override
	public void addAnnotation(final AnnotationInfo annotationInfo,
			final int parameterIndex) {
		Collection<AnnotationInfo> infos = parameterAnnotations
				.get(parameterIndex);

		if (infos == null) {
			infos = new HashSet<AnnotationInfo>();
			parameterAnnotations.put(parameterIndex, infos);
		}

		infos.add(annotationInfo);
	}

	private void checkExceptionsResolving() {
		if (exceptions.size() == 0 && exceptionClasses.size() > 0) {
			for (final String canonicalName : exceptionClasses) {
				final ClassInfo info = classPool.resolveClass(canonicalName);

				if (info != null) {
					exceptions.add(info);
				} else {
					LOG.warn("Exception-Type '" + canonicalName
							+ "' could not be resolved. Please "
							+ "ensure that it is in search classpool.");
				}
			}
			exceptionClasses.clear();
		}
	}

	private void checkParameterAnnotationsResolving() {
		if (paramAnnotations == null && parameterAnnotations.size() > 0) {
			paramAnnotations = new AnnotationInfo[parameterAnnotations.size()][];
			for (final Entry<Integer, Collection<AnnotationInfo>> entry : parameterAnnotations
					.entrySet()) {

				final int index = entry.getKey();
				paramAnnotations[index] = new AnnotationInfo[entry.getValue()
						.size()];

				int pos = 0;
				for (final AnnotationInfo info : entry.getValue()) {
					paramAnnotations[index][pos++] = info;
				}
			}

			parameterAnnotations.clear();
		}
	}

}
